/*
 Copyright (C) 2010 Chris Hawkins. All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 
 * Redistributions of source code must retain the above copyright notice, this
 list of conditions and the following disclaimer.
 
 * Neither the name of the author nor the names of its contributors may be used
 to endorse or promote products derived from this software without specific
 prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#import "AsdRequest.h"
#import "AsdMacro.h"
#import "AsdNotification.h"
#import "AsdUtil.h"
#import "AsdSignatureProvider.h"

@implementation ASDRqBase (NSOperationMain)
#pragma mark -- private utility methods call from NSOperation thread
- (void)                    mtSetResult: (ASDSpResult*) result_ {
    result = result_;       // this is a static-owned singleton class, thus no reference counting
}
- (void)                    mtAddParameter: (NSString*) parmValue_ key: (NSString*) parmKey_ {
    [dictParameters setValue: parmValue_ forKey: parmKey_];
}
- (void)                    mtRemoveParameter: (NSString*) parmKey_ {
    [dictParameters removeObjectForKey: parmKey_];
}
- (void)                    mtParmAction {
    [self mtAddParameter: [[self class] kOperationName] key: [ASDSpConst rqpAction]];
}
- (void)                    mtParmAccessKeyId {
    [self mtAddParameter: [signatureProvider accessKeyID] key: [ASDSpConst rqpAWSAccessKeyId]];
}
- (void)                    mtParmSignature {
    [self mtRemoveParameter: [ASDSpConst rqpSignature]];
    NSMutableString *signedString = [[NSMutableString alloc] initWithCapacity: 300];
    NSString        *signature    = [[signatureProvider requestSignature: dictParameters 
                                                                    verb: [ASDSpConst verbGet]
                                                                    host: [endpoint host]
                                                                  signed: signedString] retain];
    [self mtAddParameter: signature  key: [ASDSpConst rqpSignature]];
    [signatureSource    release];
    signatureSource = [[NSString alloc] initWithString: signedString];
    
    [signature          release];
    [signedString       release];
}
- (void)                    mtParmTimeStamp {
    NSDateFormatter *format = [[ASDSpUtil awsGmtDateFormatter] retain];
    NSDate          *date   = [[NSDate date] retain];
    NSString        *string = [[format stringFromDate: date] retain];
    [self mtAddParameter: string key: [ASDSpConst rqpTimestamp]]; 
    [string release];
    [date   release];
    [format release];
}
- (void)                    mtParmSignatureHack {
    // ??? If the base64 encoded signature has a '+' in it, the request will fail.  This is no doubt due to
    //     the URL-encoding issue discussed in the 'SimpleDB Developer Guide' on page 28, but I am not clear how
    //     to do the correct fix.  So this is a hack that just keeps building a signature until one without a '+'
    //     is generated.
    for ( NSUInteger loop = 1; loop < 100; loop++) {
        NSString    *signature = [self getParameterByKey: [ASDSpConst rqpSignature]];
        NSRange      range     = [signature rangeOfString: @"+"];
        if ( range.length == 0 ) {
            break;
        }
        [self mtAddParameter: [NSString stringWithFormat: @"%u",(loop + 1)] key: @"SigHack"];
        [self mtParmTimeStamp];
        [self mtParmSignature];
    }
}
- (void)                    mtParmVersion {
    [self mtAddParameter: [ASDSpConst rqvVersion] key: [ASDSpConst rqpVersion]];
}
- (void)                    mtCreateRequest {
    NSString    *queryString    = [[ASDRqBase sortedQueryString: dictParameters encode: FALSE] retain];
    NSString    *path           = [[NSString stringWithFormat: @"/?%@", queryString] retain];
    
    requestUrl = [[NSURL alloc] initWithScheme: [ASDSpConst schemeHttps] host: [endpoint host] path: path];
    urlRequest = [[NSURLRequest alloc] initWithURL: requestUrl cachePolicy: NSURLRequestReloadIgnoringCacheData timeoutInterval: 60];
    
    [queryString release];
    [path        release];
// Uncomment to see the request in the output log
//  [ASDSpUtil logUrlRequestForRequest: self];
}
- (void)                    mtLaunchRequest {
    responseDataBuf = [[NSMutableData alloc] initWithCapacity: 1000];
    urlConnection   = [[NSURLConnection alloc] initWithRequest: urlRequest delegate: self];
}
- (void)                    mtWaitOnRequest {
    NSInteger   loopCount = 0;
    NSRunLoop  *runLoop   = [NSRunLoop currentRunLoop];
    do {
        [runLoop runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1]];
        if ( endTime != nil ) {
            break;
        }
        if ( [self isCancelled] ) {
            [self mtSetResult: [ASDSpResult resultDoneCancel]];
             endTime = [[NSDate date] retain];
        }
        loopCount++;
        if ( 100 < loopCount ) {
            [self mtSetResult: [ASDSpResult resultDoneCancel]];
            endTime = [[NSDate date] retain];
        }     
    } while (true);
}
- (void)                    mtCleanUpAfterRequest {
    mASDreleaseNil( urlConnection );
}
- (void)                    mtOptionalParse {
    ASDPrBase*  parser_  = [[self parser] retain];
    if ( parser_ != nil ) {
        [parser_ parseData: [self responseData] ];
    }
    [parser_ release];
}

#pragma mark -- NSOperation
- (void)                    main {
    NSAutoreleasePool   *pool       = [[NSAutoreleasePool alloc] init];
    [[NSThread currentThread] setName: [NSString stringWithFormat: @"%u %@",[self sequenceNumber],[[self class] kOperationName]]];
    
    startTime = [[NSDate date] retain];
    
    [self mtSetResult: [ASDSpResult resultRunning]];
    [ASDSpNotification postIdleRequestNotification: self];
    
    [self mtParmAction];
    [self mtParmAccessKeyId];
    [self mtParmTimeStamp];
    [self mtParmVersion];
    
    // This must come after all the other query parameters are present.  
    [self mtParmSignature];
    
    // This should not be here, but need to get something working
    [self mtParmSignatureHack];
    
    [self mtCreateRequest];
    [self mtLaunchRequest];
    [self mtWaitOnRequest];
    [self mtCleanUpAfterRequest];
    
    // Parse is optional - only done if caller has supplied an ASDPrBase object
    [self mtOptionalParse];
    
    [ASDSpNotification postIdleRequestNotification: self];
    [pool release];
}
#pragma mark -- NSURLConnection delegate methods
- (void)                connection: (NSURLConnection*) connection_ didFailWithError: (NSError*) error_ {
    [connectionError release];
    [endTime         release];
    connectionError = [error_ retain];
    endTime         = [[NSDate date] retain];
    [self mtSetResult: [ASDSpResult resultDoneError]];
}
- (void)                connection: (NSURLConnection*) connection_ didReceiveData: (NSData*) data_ {
    [responseDataBuf appendData: data_];
}
- (void)                connection: (NSURLConnection*) connection_ didReceiveResponse: (NSURLResponse*) response_ {
    [httpResponse release];
    if ( [response_ isKindOfClass: [NSHTTPURLResponse class]] ) {
        httpResponse = (NSHTTPURLResponse*) response_;
        [httpResponse retain];
// Uncomment to see the response in the output log
//      [ASDSpUtil logHttpResponseForRequest: self];
    }
}
- (void)                connectionDidFinishLoading: (NSURLConnection*) connection_  {
    [endTime release];
    endTime = [[NSDate date] retain];
    [self mtSetResult: [ASDSpResult resultDoneOk]];
    if ( responseDataBuf != nil && 0 < [responseDataBuf length] ) {
        NSString *asString = [[NSString alloc] initWithData: responseDataBuf encoding: NSUTF8StringEncoding];
        [asString release];
    }
}
@end
